#include "fxcc/core/graph/directX11/Win32App.h"
#include "fxcc/core/graph/directX11/RenderStates.h"

using namespace fxcc::graph::directX11;


fxcc::graph::directX11::Win32App::Win32App(const Desc& desc)
    :platform::win32::App(desc)
{
}

bool fxcc::graph::directX11::Win32App::Init()
{
    if (!InitWin32())
    {
        ztclog::info("failed init win32");
        return false;
    }
    if (!InitDirectX11())
    {
        ztclog::warn("failed int directX 11");
        return false;
    }


    return true;
}

bool fxcc::graph::directX11::Win32App::InitDirectX11()
{
    if (!InitDevice())
    {
        ztclog::info("Failed int device");
        return false;
    }

    return true;
}

bool fxcc::graph::directX11::Win32App::InitDevice()
{
    HRESULT hr;

    RECT rc;
    GetClientRect(m_hWnd, &rc);

    UINT createDeviceFlags = 0;
#ifdef _DEBUG
    createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

    D3D_DRIVER_TYPE driverTypes[] =
    {
        D3D_DRIVER_TYPE_HARDWARE,
        D3D_DRIVER_TYPE_WARP,
        D3D_DRIVER_TYPE_REFERENCE,
    };
    UINT numDriverTypes = ARRAYSIZE(driverTypes);

    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
    };
    UINT numFeatureLevels = ARRAYSIZE(featureLevels);

    for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; driverTypeIndex++)
    {
        g_driverType = driverTypes[driverTypeIndex];
        hr = D3D11CreateDevice(nullptr, g_driverType, nullptr, createDeviceFlags, featureLevels, numFeatureLevels,
            D3D11_SDK_VERSION, g_pd3dDevice.GetAddressOf(), &g_featureLevel, g_pImmediateContext.GetAddressOf());

        if (hr == E_INVALIDARG)
        {
            hr = D3D11CreateDevice(nullptr, g_driverType, nullptr, createDeviceFlags, &featureLevels[1], numFeatureLevels - 1,
                D3D11_SDK_VERSION, g_pd3dDevice.GetAddressOf(), &g_featureLevel, g_pImmediateContext.GetAddressOf());
        }

        if (SUCCEEDED(hr))
            break;
    }
    if (FAILED(hr))
        return false;

    {
        ComPtr<IDXGIDevice> dxgiDevice;
        hr = g_pd3dDevice->QueryInterface(IID_PPV_ARGS(&dxgiDevice));
        if (SUCCEEDED(hr))
        {
            ComPtr<IDXGIAdapter> adapter;
            hr = dxgiDevice->GetAdapter(&adapter);
            if (SUCCEEDED(hr))
            {
                hr = adapter->GetParent(IID_PPV_ARGS(dxgiFactory.GetAddressOf()));
            }
        }
    }
    if (FAILED(hr))
        return false;

    hr = dxgiFactory->QueryInterface(IID_PPV_ARGS(dxgiFactory2.GetAddressOf()));


    int width = m_Desc.m_Size.x;
    int height = m_Desc.m_Size.y;

    if (dxgiFactory2)
    {
        hr = g_pd3dDevice->QueryInterface(IID_PPV_ARGS(&g_pd3dDevice1));
        if (SUCCEEDED(hr))
        {
            (void)g_pImmediateContext->QueryInterface(IID_PPV_ARGS(g_pImmediateContext1.GetAddressOf()));
        }

        DXGI_SWAP_CHAIN_DESC1 sd = {};
        sd.Width = width;
        sd.Height = height;
        sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.BufferCount = 1;

        hr = dxgiFactory2->CreateSwapChainForHwnd(g_pd3dDevice.Get(), m_hWnd, &sd, nullptr, nullptr, g_pSwapChain1.ReleaseAndGetAddressOf());
        if (SUCCEEDED(hr))
        {
            hr = g_pSwapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain));
        }
    }
    else
    {
        DXGI_SWAP_CHAIN_DESC sd = {};
        sd.BufferCount = 1;
        sd.BufferDesc.Width = width;
        sd.BufferDesc.Height = height;
        sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.BufferDesc.RefreshRate.Numerator = 60;
        sd.BufferDesc.RefreshRate.Denominator = 1;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.OutputWindow = m_hWnd;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.Windowed = TRUE;

        hr = dxgiFactory->CreateSwapChain(g_pd3dDevice.Get(), &sd, g_pSwapChain.GetAddressOf());
    }

    if (FAILED(hr))
        return false;

    if (dxgiFactory2)
    {
        hr = g_pd3dDevice->QueryInterface(IID_PPV_ARGS(g_pd3dDevice1.GetAddressOf()));
        if (SUCCEEDED(hr))
        {
            (void)g_pImmediateContext->QueryInterface(IID_PPV_ARGS(g_pImmediateContext1.GetAddressOf()));
        }

        DXGI_SWAP_CHAIN_DESC1 sd = {};
        sd.Width = width;
        sd.Height = height;
        sd.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.BufferCount = 1;

        hr = dxgiFactory2->CreateSwapChainForHwnd(g_pd3dDevice.Get(), m_hWnd, &sd, nullptr, nullptr, g_pSwapChain1.ReleaseAndGetAddressOf());
        if (SUCCEEDED(hr))
        {
            hr = g_pSwapChain1->QueryInterface(IID_PPV_ARGS(&g_pSwapChain));
        }
    }
    else
    {
        DXGI_SWAP_CHAIN_DESC sd = {};
        sd.BufferCount = 1;
        sd.BufferDesc.Width = width;
        sd.BufferDesc.Height = height;
        sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        sd.BufferDesc.RefreshRate.Numerator = 60;
        sd.BufferDesc.RefreshRate.Denominator = 1;
        sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        sd.OutputWindow = m_hWnd;
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
        sd.Windowed = TRUE;

        hr = dxgiFactory->CreateSwapChain(g_pd3dDevice.Get(), &sd, g_pSwapChain.ReleaseAndGetAddressOf());
    }

    auto& rs = fxcc::graph::directX11::RenderStates::Inst();
    rs.m_Device = g_pd3dDevice.Get();
    rs.m_Context = g_pImmediateContext.Get();

    fxcc::graph::common::RenderStates::Inst().Init();
    fxcc::graph::directX11::RenderStates::Inst().Init();

    m_HasInitFlag = true;

    OnFramebuffersize(width, height);

    return true;
}

int fxcc::graph::directX11::Win32App::Run()
{
    if (!Init())
    {
        ztclog::info("failed init app");
        return 1;
    }
    bool done = false;
    while (!done)
    {
        MSG msg;
        while (::PeekMessage(&msg, nullptr, 0U, 0U, PM_REMOVE))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);

            if (msg.message == WM_QUIT)
                done = true;
        }

        OnFrameRender();

        OnUpdate();
        
        m_Input.Clear();

        if (done)
            break;

        g_pSwapChain->Present(m_Desc.m_Interval, 0);

    }

    OnDestory();

    return true;

}

void fxcc::graph::directX11::Win32App::OnFrameRender()
{


}

void fxcc::graph::directX11::Win32App::OnUpdate()
{
    fxcc::platform::win32::App::OnUpdate();
}

void fxcc::graph::directX11::Win32App::OnDestory()
{
    fxcc::platform::win32::App::OnDestroy();

}

void fxcc::graph::directX11::Win32App::OnClose()
{
}

void fxcc::graph::directX11::Win32App::OnFramebuffersize(int w, int h)
{
    fxcc::platform::win32::App::OnFramebuffersize(w, h);

    if (!OnResize(w, h))
    {
        ztclog::info("failed create resources depend on window size");
    }

}

bool fxcc::graph::directX11::Win32App::HasInitApp()
{
    return m_HasInitFlag;
}

bool fxcc::graph::directX11::Win32App::OnResize(int width, int height)
{
    if (!m_HasInitFlag)
    {
        ztclog::info("Waiting ID3D11Device Context Init");
        return true;
    }

    HRESULT hr;

    g_pRenderTargetView.Reset();

    if (g_pSwapChain)
    {
        g_pSwapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
    }
    else if (g_pSwapChain1)
    {
        g_pSwapChain1->ResizeBuffers(1, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);
    }

    ComPtr<ID3D11Texture2D> pBackBuffer;
    hr = g_pSwapChain->GetBuffer(0, IID_PPV_ARGS(pBackBuffer.ReleaseAndGetAddressOf()));
    if (FAILED(hr))
        return false;

    hr = g_pd3dDevice->CreateRenderTargetView(pBackBuffer.Get(), nullptr, g_pRenderTargetView.ReleaseAndGetAddressOf());
    if (FAILED(hr))
        return false;

    return true;
}
